<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>作用域与作用域链</title>
  </head>
  <body>
    <h1>作用域与作用域链</h1>
    <p>
      在编程语言中，作用域和作用域链是非常重要的概念，它们决定了变量的可访问性和生命周期，以下是对它们的详细介绍：
    </p>
    <h2>一、作用域</h2>
    <h3>定义</h3>
    <p>
      作用域是程序源代码中定义变量的区域。它规定了变量的可访问范围，即在哪些代码块内可以访问该变量。不同的编程语言有不同的作用域规则。
    </p>
    <p>
      例如，在 JavaScript
      中，主要有函数作用域和块级作用域。函数作用域是指在函数内部定义的变量只能在该函数内部被访问。块级作用域则是指在代码块（如
      if 语句块、for 循环块等）内部定义的变量只能在该代码块内部被访问。在 C
      语言中，变量的作用域主要由其定义的位置决定，如果在函数内部定义，则为局部变量，作用域仅限于该函数；如果在所有函数之外定义，则为全局变量，作用域是整个程序（除了被局部变量遮蔽的情况）。
    </p>

    <p>作用域的分类</p>
    <p>全局作用域</p>
    <p>
      在全局作用域中定义的变量在整个程序中都可访问。例如，在 Python
      脚本的最外层定义的变量就是全局变量。这些变量在程序的任何地方都可以被引用，除非它们被局部变量遮蔽。全局变量的生命周期通常从程序开始执行一直持续到程序结束。
    </p>
    <p>局部作用域</p>
    <p>
      局部作用域是指在特定的代码结构（如函数、类、代码块等）内部定义的变量的作用域。在函数内部定义的局部变量只能在函数内部使用。例如，在
      Java
      方法中定义的变量，当方法执行完毕后，这些局部变量就会被销毁，它们的作用域仅限于方法的花括号内部。局部作用域的变量有助于避免命名冲突，因为不同局部作用域中的变量可以有相同的名称，而不会相互干扰。
    </p>
    <p>作用域的作用</p>
    <p>封装和隐藏</p>
    <p>
      作用域可以将变量封装起来，隐藏其内部实现细节。例如，在面向对象编程中，类的成员变量通常具有私有作用域，外部代码无法直接访问这些私有变量，只能通过类提供的公共方法来间接访问和修改，这样可以保护数据的完整性和安全性。
    </p>
    <p>避免命名冲突</p>
    <p>
      在大型程序中，可能会有多个开发者同时编写代码。作用域可以确保不同代码块中的变量即使名称相同也不会相互干扰。比如在一个大型的
      JavaScript
      项目中，不同的模块可以定义自己的局部变量，即使模块之间有相同名称的变量，由于它们处于不同的作用域，所以不会发生冲突，这有助于代码的模块化和可维护性。
    </p>
    <p>二、作用域链</p>

    <p>定义</p>
    <p>
      作用域链是编程语言在查找变量时所遵循的一系列作用域的顺序。当在当前作用域中找不到某个变量时，解释器或编译器会沿着作用域链向上查找，直到找到该变量或者到达全局作用域为止。
    </p>
    <p>
      例如，在 JavaScript
      中，当你在一个嵌套函数内部访问一个变量时，如果该变量在嵌套函数内部没有定义，解释器就会在嵌套函数的外部函数中查找，如果还是没有找到，就会继续在更外层的作用域中查找，直到到达全局作用域。这个查找过程就是沿着作用域链进行的。
    </p>
    <p>作用域链的形成</p>
    <p>
      作用域链的形成与代码的嵌套结构有关。在嵌套的代码块（如嵌套的函数、嵌套的类等）中，内部代码块的作用域会包含外部代码块的作用域。例如，在
      JavaScript 中，当你定义一个函数 A，在函数 A 内部定义另一个函数 B 时，函数
      B 的作用域链就包含了函数 A 的作用域和全局作用域。函数 B 可以访问在函数 A
      中定义的变量，也可以访问全局变量。这种作用域链的形成使得变量的查找有了一定的顺序和规则。
    </p>
    <p>
      在类的继承关系中，也会形成类似的作用域链。例如，在面向对象编程语言中，子类可以访问父类的公有成员变量和方法。从作用域链的角度来看，子类的作用域链包含了父类的作用域，这样子类对象就可以沿着作用域链向上查找父类中的成员。
    </p>

    <p>作用域链的影响</p>
    <p>性能影响</p>
    <p>
      作用域链的长度会影响变量查找的性能。如果作用域链很长，查找变量就需要花费更多的时间。例如，在一个嵌套很深的函数结构中，如果要访问一个定义在最外层函数中的变量，解释器就需要沿着很长的作用域链逐层查找，这可能会导致性能下降。在编写代码时，尽量避免过深的嵌套结构，可以使变量查找更高效。
    </p>
    <p>闭包相关</p>
    <p>
      作用域链在闭包中起着关键作用。闭包是一个函数和其周围的状态（词法环境）的组合。在
      JavaScript
      中，当一个内部函数被外部函数返回时，就形成了闭包。闭包可以捕获外部函数的作用域链，即使外部函数已经执行完毕，内部函数仍然可以访问外部函数中的变量。这是因为闭包保留了对作用域链的引用，使得外部函数的变量不会被销毁，这对实现一些功能（如私有变量、模块化等）非常有用，但也可能导致内存泄漏等问题，因为闭包可能会阻止一些不再需要的变量被垃圾回收。
    </p>
  </body>
  <script>
    // 定义一个全局变量
    var globalVar = "我是全局变量";

    // 定义一个外部函数
    function outerFunction() {
      // 在外部函数中定义一个局部变量
      var outerVar = "我是外部函数的局部变量";

      // 定义一个内部函数
      function innerFunction() {
        // 在内部函数中定义一个局部变量
        var innerVar = "我是内部函数的局部变量";

        // 访问内部函数的局部变量
        console.log(innerVar); // 输出：我是内部函数的局部变量

        // 访问外部函数的局部变量
        console.log(outerVar); // 输出：我是外部函数的局部变量

        // 访问全局变量
        console.log(globalVar); // 输出：我是全局变量
      }

      // 调用内部函数
      innerFunction();
    }

    // 调用外部函数
    outerFunction();

    // 尝试在全局作用域中访问外部函数的局部变量
    // console.log(outerVar); // 这里会报错，因为 outerVar 只在外部函数的作用域内有效

    // 面试题：请尝试在全局作用域中访问内部函数的局部变量
    // console.log(innerVar); // 这里会报错，因为 innerVar 只在内部函数的作用域内有效

    // 面试题：介绍一下闭包
    // 闭包是一个函数和其周围状态（词法环境）的组合
    // 闭包可以捕获外部函数的作用域链，即使外部函数已经执行完毕，内部函数仍然可以访问外部函数中的变量

    // 面试题：简单介绍一下作用域和作用域链
    // 作用域：定义了变量的可访问范围
    // 作用域链：解释器在查找变量时所遵循的一系列作用域的顺序
    // 当在当前作用域中找不到某个变量时，解释器会沿着作用域链向上查找，直到找到该变量或者到达全局作用域为止，这个查找过程就是沿着作用域链进行的，当在全局作用域中找不到该变量时，就会报错

    // 面试题：请解释一下作用域链的形成过程
    // 作用域链的形成与代码的嵌套结构有关


  </script>
  </script>
</html>
